package spring.boot;


import org.springframework.beans.BeanMetadataAttribute;
import org.springframework.beans.BeansException;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.lang.NonNull;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
import spring.boot.annotation.EnableAutoConfiguration;
import spring.boot.annotation.EnableInto;
import spring.boot.annotation.PropertiesInto;
import spring.boot.factory.WebServletFactory;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.util.List;
import java.util.Properties;

/**
 * @ClassName MySpringApplication
 * @Description TODO
 * @Author Toolck Rin
 * @Date 2021/3/10 11:21
 * @Version 1.0
 *
 * 1、加载配置文件
 * 2、创建容器
 * 3、加载配置类,将配置类自动封装为bean注入容器中。
 * 4、启动容器
 * 6、创建tomcat,并设置其连接参数
 * 7、配置DispatcherServlet
 * 8、启动tomcat
 * 9、将DispatcherServlet添加到tomcat中即可
 */
public class SpringApplication {
    private static Class<?> sourceClass;
    public static AnnotationConfigApplicationContext registry;
    private static AnnotationConfigWebApplicationContext context;
    private ClassLoader classLoader;
    private Properties properties;
    private final String LOCAL_PATH = "application.properties";


    public SpringApplication(@NonNull Class<?> appConfigClass1) {
        // 1、解析配置类的信息封装起来
        sourceClass = appConfigClass1;
    }

    public static void run(Class<?> appConfigClass, String[] args) {
        new SpringApplication(appConfigClass).run(args);
    }

    /*
     * 1、加载配置文件
     * 2、创建容器
     * 3、加载配置类,将配置类自动封装为bean注入容器中。
     * 4、启动容器
     * 6、创建tomcat,并设置其连接参数
     * 7、配置DispatcherServlet
     * 8、启动tomcate
     * 9、将DispatcherServlet添加到tomcat中即可
     */
    private void run(String[] args) {
        // 1、加载配置文件
        localProperties();
        //  2、创建容器
        creatApplicationContext();
        //  3、加载配置类自动配置的类（WebServletFactory）并封装为对象
        loadSource();
        //  4、启动容器
        startContext();
        // 5、创建servlet
        creatDispatcherServlet();
        // 6、创建tomcat并启动
        createWebServer();
    }

    private void creatDispatcherServlet() {
        DispatcherServlet dispatcherServlet = new DispatcherServlet(context);
        registry.getBeanFactory().registerSingleton("dispatcherServlet",dispatcherServlet);
    }

    private void startContext() {
        registry.refresh();
        context.refresh();
    }

    private void localProperties() {
        try {
            InputStream resourceAsStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(LOCAL_PATH);
            properties = new Properties();
            properties.load(resourceAsStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void createWebServer() {
        WebServletFactory bean = registry.getBean(WebServletFactory.class);
        bean.createServer();
    }


    private void loadSource() {
        List<String> enableAutoConfigurations = SpringFactoriesLoader.loadFactoryNames(EnableAutoConfiguration.class, classLoader);
        enableAutoConfigurations.forEach(name -> {
            try {
                GenericBeanDefinition bd = new GenericBeanDefinition();
                Class<?> beanClass = Class.forName(name);
                bd.setBeanClass(beanClass);
                enableIntoAnntation(bd);
                BeanDefinitionReaderUtils.registerWithGeneratedName(bd, registry);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        });
    }

    private void enableIntoAnntation(GenericBeanDefinition bd) {
        Class<?> beanClass = bd.getBeanClass();
        if (beanClass.isAnnotationPresent(EnableInto.class)) {
            EnableInto annotation = beanClass.getAnnotation(EnableInto.class);
            propertiesIntoIntoAnntation(annotation.value());
        }
    }

    private void propertiesIntoIntoAnntation(Class<?> aClass) {
        if (aClass.isAnnotationPresent(PropertiesInto.class)) {
            PropertiesInto annotation = aClass.getAnnotation(PropertiesInto.class);
            GenericBeanDefinition bd = new GenericBeanDefinition();
            MutablePropertyValues mutablePropertyValues = new MutablePropertyValues();
            bd.setBeanClass(aClass);
            String per = annotation.value();
            Field[] declaredFields = aClass.getDeclaredFields();
            for (int i = 0; i < declaredFields.length; i++) {
                Field declaredField = declaredFields[i];
                String name = declaredField.getName();
                Object value = properties.get(per + "." + name);
                if (value != null) {
                    mutablePropertyValues.add(name, value);
                }
            }
            bd.setPropertyValues(mutablePropertyValues);
            BeanDefinitionReaderUtils.registerWithGeneratedName(bd, registry);
        }
    }

    private void creatApplicationContext() {
        context = new AnnotationConfigWebApplicationContext();
        if (sourceClass == null) {
            throw new RuntimeException("注册的类不能为空");
        }
        AnnotationConfigApplicationContext root = new AnnotationConfigApplicationContext();
        root.register(sourceClass);
        context.register(sourceClass);
        context.setParent(root);
        registry = root;
    }


}
